#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##-- G06 -- S04 ------------------------------------------------------------##
__all__ = []

from .. import autostacks
from .. import formlib as layout
from .. import pilelib as stack
from .. import statdb
from ..gamelib import info as gi, state, game
from ..hint import DefaultHint, CautiousDefaultHint
from ..packlib import info as cs
from ..settings import CONF_OPT
from . import golf

GT, SL = gi.GT, gi.SL
Wrap = stack.StackWrapper


class Talon(stack.WasteTalon):
    """
    Deal _waste_. Reset _waste_. Score seq play before deal and reset _waste_
    cards play.
    """

    @state.move_state('deal')
    def deal_cards(self, sound=False):
        """
        deal_cards([sound:bool=False]) -> None

        Deal cards for Game.talon_deal() to other piles. Before deal
        num_cards to _waste_, update score for current sequence. After reset
        sequence.

        :keyword boolean sound: sound fx?
        """
        score, sequence = self.game.status
        # Add total score for sequence
        if sequence:
            i = sum(2 for r in self.game.s['rows'][:3] if not r.cards) or 1  # Peaks
            score += i * 2 ** ((sequence - 1) / 4)
        n = super(Talon, self).deal_cards(sound)
        self.game.status = [score, 0]
        return n


class Waste(golf.UD_RK_Waste):
    """
    Build either in rank alone. Play top. Increment _wastecards play.
    """

    def closeStack(self):
        """
        closeStack() -> None

        Overide to perfom after addCard or insertCard a seqeunce increment.
        """
        self.game.status[1] += 1  # Increment sequence


class RowStack(stack.SingleAsDoubleClickMethod, stack.OpenStack):
    """
    Build none. Play top. Block piles in group _row_ above. Single is double
    click.
    """

    def cardsBlock(self):
        """
        cardsBlock() -> bool

        Check if pile is blocked by piles below.

        :return: pile is blocked?
        :rtype: boolean
        """
        step = (3, 4, 5, 6, 6, 7, 7, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9,)
        i = self.id
        while i < 18:
            i += step[i]
            if any(self.game.s['rows'][i + j].cards for j in range(2)):
                return True
        return False


class FirTrees_RowStack(golf.RowStack):
    _BOTTOM_IMAGE_ = None

    def __init__(self, x, y, game, **cap):
        """
        __init__(x:int, y:int, game:obj[, offset:tup=(0, 3), *capability*
          {max_cards:int=2}]) -> None

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :param integer suit: pile's suit and base_suit
        :keyword tuple offset: offset from left and offset from top

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.
        """
        cap.setdefault('offset', (0, game.images.offset.yoffset))
        super(FirTrees_RowStack, self).__init__(x, y, game, **cap)
        self.blockmap = []

    def cardsBlock(self):
        """
        cardsBlock() -> bool

        Check if pile is blocked by seeing if piles of id have cards.

        :return: pile is blocked?
        :rtype: boolean
        """
        return any([self.game.s['rows'][i].cards for i in self.blockmap])


def setFirTree_iter(l, sx, sy, size):
    """
    setFirTree_iter(l:obj, sx:int, sy:int, size:int) -> iter

    Iter coords for piles in a Fir Tree form.
    """
    for i in range(size):
        x = sx + ((i + 1) % 2) * l.XS / 2
        y = sy + i * l.YS / 4
        for j in ((i % 2) + 1):
            yield x, y
            x += l.XS


def mapFirTree(length, size):
    """
    mapFirTree(length:int, size:int) -> iter

    Iter a block map for piles in one or many Fir Tree forms.
    """
    size -= 1
    for n in range(0, length, length):
        for i in range(size - 1):
            if i % 2:
                yield n, (n + 2,)
                yield n + 1, (n + 2,)
                n += 2
            else:
                yield n, (n + 1, n + 2,)
                n += 1


class ThreePeaksNoScore(game.Game):
    """
    Clear cards from _row_ within a turn.

    _row_
      Pile 28, 3 peaks, 4 high. Init deal 1 close card, except last row. Build
      none. Play top.

    _talon_
      Pile 1 S. Deal 1 card on _waste_.

    _waste_
      Pile 1 E _talon_. Init deal 1 card. Build either in rank alone wrap.
      Play none.
    ------
    :cvar Stack _talon_: stack.WasteTalon
    :cvar Stack _waste_: golf.UD_RK_Waste (mod=13)
    :cvar Stack _row_: RowStack
    :cvar Hint _hint_: golf.Hint
    """
    __info__ = dict(id=22231, name='Three Peaks (No score)',
                    game_type=GT.PAIRING, decks=1, redeals=0,
                    skill_level=SL.BALANCED, version='0.50fc')
    __author__ = 'Mt. Hood'
    __version__ = 7.70

    _talon_ = Wrap(stack.WasteTalon, num_deal=1, max_rounds=1)
    _waste_ = Wrap(golf.UD_RK_Waste, mod=13)
    _row_ = RowStack
    _hint_ = golf.Hint

    def _create_game(self, **kw):
        l, s = layout.Layout(self), self.s
        xoffset = int(l.XS * 8 / l.NCARDS)
        offset = xoffset if xoffset < l.XOFFSET else l.XOFFSET
        self.table_size = (l.XM + l.XS * 10, l.YM + l.YS * 4,)
        dx = l.XM + 3 * l.HXS
        x, y = dx, l.YM
        for row in ([0, 3, 6], [0, 1, 3, 4, 6, 7],
                    range(9), range(10)):  # Vert
            x = dx
            for j in row:
                x = j * l.XS
                s['rows'].append(self._row_(x, y, game=self))
            dx -= l.HXS
            y += l.HYS
        x, y = l.XM, y + l.YM + l.YS
        s['talon'] = self._talon_(x, y, self)
        l.set_ncards_text(s['talon'], 's')
        x = x + l.XS
        s['wastes'].append(self._waste_(x, y, self, offset=(offset, 0)))
        s['foundations'] = s['wastes']
        l.set_ncards_text(s['wastes'][-1], 's')
        self.texts['score'] = l.create_score_text(None, 'sw',
                                                  dx=l.XM + l.XS * 3,
                                                  dy=self.height - l.YM)
        l.defaultStackGroups()
        return l

    def _start(self, **kw):
        self.status = [-self['ncards'], 0]
        self._play_sound('deal')
        self.s['talon'].dealRow(rows=self.s['rows'][:18], flip=False, frames=4)
        self.s['talon'].dealRow(rows=self.s['rows'][18:], flip=True, frames=4)
        self.s['talon'].deal_cards()

    @property
    def is_complete(self):
        if any(r.cards  for r in self.s['rows']):
            return False
        score, sequence = self.status
        if sequence:
            self.status = [score + len(self.s['talon'].cards) * 10, sequence]
        self._update_text()
        return True


class ThreePeaks(ThreePeaksNoScore):
    """
    Clear cards from _row_ within a turn.

    _row_
      Pile 28, 3 peaks, 4 high. Init deal 1 close card, except last row. Build
      none. Play top.

    _talon_
      Pile 1 S. Deal 1 card on _waste_.

    _waste_
      Pile 1 E _talon_. Init deal 1 card. Build either in rank alone wrap.
      Play none.

    note::
      ⟪undo⟫ disabled.

      Max score 2336 pts. Game debits 52 pts. 1st 4 card seq credits 1 pt.
      Pts ×2 each 4 card seq after. 1st peak clear credits 10 pts and ×2
      curr score. Pts and score ×2 each peak clear after. Cards remain in
      _talon_, after _row_ clear credits 10 pts each.
    ------
    :cvar Stack _talon_: Talon
    :cvar Stack _waste_: Waste (mod=13)
    :cvar Stack _row_: RowStack
    :cvar Hint _hint_: golf.Hint
    """
    __info__ = dict(id=22216, name='Three Peaks', game_type=(GT.PAIRING |
                    GT.SCORE), decks=1, redeals=0, skill_level=SL.BALANCED,
                    altnames=('Tri Peaks',), version='0.50fc')
    __author__ = 'Mt. Hood'
    __credits__ = ['Robert Hogue']
    __version__ = 7.70

    _talon_ = Talon
    _waste_ = Wrap(Waste, mod=13)

    @state.not_state('is_preview')
    def _update_text(self):
        if not self.texts['info']:
            return
        b1 = statdb.STAT_DB.results(CONF_OPT['player'], self['id'], 'score',
                                    'sum', all_=False)
        b2 = self.score
        t = _('Score:\011This hand: {}').format(b2)
        t += _('\011This game: {}').format(b1 + b2)
        self.texts['info'].config(text=t)

    @property
    def score(self):
        # Empty peaks
        i = sum(2 for r in self.s['rows'][:3] if not r.cards) or 1
        # Score peaks
        peaks = sum(5 * 1 for r in self.s['rows'][:3] if not r.cards)
        score, sequence = self.status
        # Score sequence
        if sequence and len(self.game.s['wastes'][-1].cards) - 1:
            score += i * 2 ** ((sequence - 1) / 4)
        return self.status[0] + score + peaks

    @property
    def can_undo_move(self):
        """
        Can we undo our moves? Default is False to prevent undo.
        """
        return False


class ThreeFirTrees(game.Game):
    """
    Clear cards from _row_ within a turn.

    _row_
      Pile 48, 3 fir-trees, 6 high. Init deal 1 card. Build none. Play top.

    _talon_
      Pile 1 SW. Deal 1 card on _waste_.

    _waste_
      Pile 1 E _talon_. Init deal 1 card. Build either in rank alone. Play
      none.

    note::
      ⟪highlightpiles⟫, ⟪quickplay⟫, ⟪dropstacks⟫ disabled.
    ------
    :cvar Stack _talon_: golf.Talon
    :cvar Stack _waste_: golf.UD_RK_Waste
    :cvar Stack _row_: FirTrees_RowStack
    :cvar Hint _hint_: hint.CautiousDefaultHint
    """
    __info__ = dict(id=731, name="Three Fir-trees", game_type=GT.GOLF,
                    decks=2, redeals=0, skill_level=SL.BALANCED,
                    version='1.10fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = golf.Talon
    _waste_ = golf.UD_RK_Waste
    _row_ = FirTrees_RowStack
    _hint_ = CautiousDefaultHint

    def _create_game(self, playcards=26, **kw):
        l, s = layout.Layout(self), self.s
        self.table_size = (
                   l.XM + l.XS + max(3 * l.XS, l._get_stack_width(playcards)),
                   l.YM + 5 * l.YS)
        ddx = 5 * l.HXS
        dx, dy = (self.width - 7 * l.XS) / 2, l.YM
        for i in range(3):
            for x, y in setFirTree_iter(l, dx, dy, size=11):
                s['rows'].append(self._row_(x, y, game=self))
            dx += ddx
        for i, blockmap in mapFirTree(len(self.s['rows']) / 3, size=11):
            s['rows'][i].blockmap = blockmap
        x, y = l.XM, self.height - l.YS
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 'n')
        x += l.XS
        s['wastes'].append(self._waste_(x, y, game=self, offset=(l.XOFFSET, 0)))
        l.set_ncards_text(s['wastes'][-1], 'n')
        s['foundations'] = s['wastes']
        l.defaultStackGroups()
        return l

    def _start(self, rows=1, **kw):
        for i in range(rows - 1):
            self.s['talon'].dealRow(frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow()
        self.s['talon'].deal_cards()

    @property
    def is_complete(self):
        return all(not r.cards for r in self.s['rows'])

    @property
    def _highlight_piles(self):
        """
        Return piles to highlight for possible move.

        :return: empty.
        :rtype: tuple
        """
        return ()

    def _autopiles(self, event=None):
        """
        Return piles for auto or quick move.

        Overide to disable quickplay when event, else autodrop, quickplay.

        .. \_autopiles(self, event=None) -> seq(seq(Pile ...), seq(Pile ...),
             seq(Pile ...))
        """
        piles = super(ThreeFirTrees, self)._autopiles(event)
        if event is None:
            return autostacks.flip(piles)
        else:
            return autostacks.flip_drop(piles)


class RelaxedThreeFirTrees(ThreeFirTrees):
    """
    Clear cards from _row_ within a turn.

    _row_
      Pile 48, 3 fir-trees, 6 high. Init deal 1 card. Build none. Play top.

    _talon_
      Pile 1 SW. Deal 1 card on _waste_.

    _waste_
      Pile 1 E _talon_. Init deal 1 card. Build either in rank alone wrap.
      Play none.

    note::
      ⟪highlightpiles⟫, ⟪quickplay⟫, ⟪dropstacks⟫ disabled.
    ------
    :cvar Stack _talon_: golf.Talon
    :cvar Stack _waste_: golf.UD_RK_Waste (mod=13)
    :cvar Stack _row_: FirTrees_RowStack
    :cvar Hint _hint_: hint.CautiousDefaultHint
    """
    __info__ = dict(id=768, name="Relaxed Three Fir-trees", game_type=GT.GOLF,
                    decks=2, redeals=0, skill_level=SL.BALANCED,
                    version='1.10fc',
                    altnames=('Three Fir-trees (Relaxed)',))
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _waste_ = Wrap(golf.UD_RK_Waste, mod=13)


class NapoleonTakesMoscow(game.Game):
    """
    Finish cards on _foundation_ within 3 turn.

    _row_
      Pile 8, E W _reserve_, S _foundation_. Init deal 4 card. Build down in
      rank by same suit. Play top. Base KING.

    _reserve_
      Pile 16, 1 fir-trees, 6 high. Init deal 1 card. Build none. Play top.

    _talon_
      Pile 1 SW. Deal 1 card on _waste_.

    _waste_
      Pile 1 E _talon_. Init deal 1 card. Build none. Play top.

    _foundation_
      Pile 8 N. Build up in rank by same suit. Play top.
    ------
    :cvar Stack _talon_: stack.WasteTalon (max_rounds=3)
    :cvar Stack _waste_: stack.WasteStack
    :cvar Stack _foundation_: stack.SS_Foundation
    :cvar Stack _row_: SS_RowStack (base_rank=KING, max_move=1)
    :cvar Stack _reserve_: FirTrees_RowStack
    :cvar Hint _hint_: hint.CautiousDefaultHint
    """
    __info__ = dict(id=733, name="Napoleon Takes Moscow",
                    game_type=GT.NAPOLEON, decks=2, redeals=2,
                    skill_level=SL.BALANCED, version='1.10fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = Wrap(stack.WasteTalon, max_rounds=3)
    _waste_ = stack.WasteStack
    _foundation_ = stack.SS_Foundation
    _row_ = Wrap(stack.SS_RowStack, base_rank=cs.KING, max_move=1)
    _reserve_ = FirTrees_RowStack
    _hint_ = CautiousDefaultHint

    def _create_game(self, rows=8, playcards=15, **kw):
        l, s = layout.Layout(self), self.s
        self.table_size = (l.XM + 10 * l.XS,
                     l.YM + 2 * l.YS + l._get_stack_height(playcards))
        x, y = l.XM + l.XS, l.YM
        for x, suit in zip(l.row_iter(l.XM + l.XS, l.NSUITS), l.SUITS):
            s['foundations'].append(self._foundation(x, y, game=self, suit=suit))
        x = l.XM
        y += l.YS
        dx, dy = l.XM, y
        for i in range(2):
            for x in l.row_iter(dx, rows / 2):
                s['rows'].append(self._row_(x, y, game=self))
            dx = x + 3 * l.XS
        for x, y in setFirTree_iter(l, dx - 3 * l.XS / 2, dy):
            s['reserves'].append(self._reserve_(x, y, game=self))
        for i, blockmap in mapFirTree(len(s.reserves), size=11):
            s['reserves'][i].blockmap = blockmap
        x, y = l.XM, self.height - l.YS
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 'n')
        l.set_rounds_text(s['talon'], 'nnn')
        x += l.XS
        s['wastes'].append(self._waste_(x, y, game=self))
        l.set_ncards_text(s['wastes'][-1], 'n')
        l.defaultStackGroups()
        return l

    def _start(self, rows=4, **kw):
        self.s['talon'].dealRow(rows=self.s['reserves'], frames=0)
        for i in range(rows - 1):
            self.s['talon'].dealRow(frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow()
        self.s['talon'].deal_cards()


class NapoleonLeavesMoscow(NapoleonTakesMoscow):
    """
    Finish cards on _foundation_ within 3 turn.

    _row_
      Pile 8, E W _reserve_, S _foundation_. Init deal 5 card. Build down in
      rank by same suit. Play seq. Base KING.

    _reserve_
      Pile 16, 1 fir-trees, 6 high. Init deal 1 card. Build none. Play top.

    _talon_
      Pile 1 SW. Deal 1 card on _waste_.

    _waste_
      Pile 1 E _talon_. Init deal 1 card. Build none. Play top.

    _foundation_
      Pile 8 N. Build up in rank by same suit. Play top.
    ------
    :cvar Stack _talon_: stack.WasteTalon (max_rounds=3)
    :cvar Stack _waste_: stack.WasteStack
    :cvar Stack _foundation_: stack.SS_Foundation
    :cvar Stack _row_: SS_RowStack (base_rank=KING)
    :cvar Stack _reserve_: FirTrees_RowStack
    :cvar Hint _hint_: hint.DefaultHint
    """
    __info__ = dict(id=734, name="Napoleon Leaves Moscow",
                    game_type=GT.NAPOLEON, decks=2, redeals=2,
                    skill_level=SL.BALANCED, version='1.10fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _row_ = Wrap(stack.SS_RowStack, base_rank=cs.KING)
    _hint_ = DefaultHint

    def _start(self, **kw):
        kw.setdefault('rows', 5)
        super(NapoleonLeavesMoscow, self)._start(**kw)


PROVIDES = [ThreePeaks, ThreePeaksNoScore, ThreeFirTrees,
            RelaxedThreeFirTrees, NapoleonTakesMoscow, NapoleonLeavesMoscow]